﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace scanner_tron_v2
{
    internal static class Finder
    {
        public enum PointState
        {
            Fail,
            Connect,
            Lock,
        }
        public class Point
        {
            public string ip;
            public uint ping;
            public PointState state;
            public uint error;
        }

        public static uint RemoteHeight
        {
            get;
            private set;
        }
        public static int usePoints;
        public static System.Collections.Concurrent.ConcurrentQueue<Point> freePoints = new System.Collections.Concurrent.ConcurrentQueue<Point>();
        public static System.Collections.Concurrent.ConcurrentQueue<string> testips = new System.Collections.Concurrent.ConcurrentQueue<string>();
        public static System.Collections.Concurrent.ConcurrentDictionary<string, int> allips = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
        //static async Task TestPingOne(string ip)
        //{
        //    try
        //    {
        //        DateTime t0 = DateTime.Now;
        //        WebClient wc = new mywc();
        //        var result = await wc.DownloadStringTaskAsync("http://" + ip + ":8091/walletsolidity/getnowblock");
        //        var json = JObject.Parse(result);
        //        var h = (uint)json["block_header"]["raw_data"]["number"];
        //        DateTime t1 = DateTime.Now;
        //        Console.WriteLine("ip " + ip + " ping=" + (t1 - t0).TotalMilliseconds + " height=" + h);
        //        lock (points)
        //        {
        //            points.Add(new Point() { ip = ip, ping = (int)(t1 - t0).TotalMilliseconds });
        //            if (RemoteHeight < h)
        //                RemoteHeight = h;

        //        }
        //    }
        //    catch
        //    {
        //        Console.WriteLine("ip " + ip + " error");
        //    }
        //}
        //public static async Task TestIP(string[] ips)
        //{
        //    points = new List<Point>();
        //    List<Task> alltasks = new List<Task>();

        //    foreach (var ip in ips)
        //    {
        //        alltasks.Add(TestPingOne(ip));
        //    }
        //    await Task.WhenAll(alltasks);

        //    points.Sort((a, b) =>
        //    {
        //        return a.ping - b.ping;
        //    }
        //    );
        //}

        public static async void BeginTestIP(string[] seedip)
        {
            foreach (var ip in seedip)
            {
                if (allips.ContainsKey(ip) == false)
                {
                    testips.Enqueue(ip);
                }
            }
            for (var i = 0; i < 10; i++)
            {
                IPFinder finder = new IPFinder();
                finder.Run(100);
            }

        }
        public static void UpdateHeight(uint height)
        {
            if (RemoteHeight < height)
                RemoteHeight = height;
        }
    }

    public class mywc : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            var req = base.GetWebRequest(address);
            req.Timeout = 5000;
            return req;
        }
    }
    public class IPFinder
    {
        mywc wc = new mywc();
        public int ping
        {
            get;
            private set;
        }
        public string useip
        {
            get;
            private set;
        }
        public struct Height
        {
            public uint ping;
            public uint height;
        }
        public async Task<Height> GetHeight(string ip)
        {
            try
            {
                DateTime t0 = DateTime.Now;

                var result = await wc.DownloadStringTaskAsync("http://" + ip + ":8091/walletsolidity/getnowblock");
                var json = JObject.Parse(result);
                var h = (uint)json["block_header"]["raw_data"]["number"];
                DateTime t1 = DateTime.Now;
                var ping = (uint)(t1 - t0).TotalMilliseconds;
                //Console.WriteLine("got ip " + ip + " ping=" + ping + " height=" + h);
                return new Height { ping = ping, height = h };
            }
            catch
            {
                //Console.WriteLine("ip " + ip + " error");
                return new Height { ping = 0, height = 0 };
            }
        }
        public async Task<uint> GetNode(string ip, List<string> resultlist)
        {
            try
            {
                DateTime time0 = DateTime.Now;
                var result = await wc.DownloadStringTaskAsync("http://" + ip + ":8090/wallet/listnodes");
                var json = JObject.Parse(result);
                foreach (var n in (json["nodes"] as JArray))
                {
                    var hex = n["address"]["host"].ToString();
                    resultlist.Add(System.Text.Encoding.ASCII.GetString(Convert.FromHexString(hex)));
                }
                var pint = (uint)(DateTime.Now - time0).TotalMilliseconds;
                if (pint <= 0)
                    pint = 1;
                return pint;
            }
            catch (Exception err)
            {
                //Console.WriteLine("ip " + ip + " error:"+err.Message);
                return 0;
            }
        }
        public async void Run(int max)
        {
            List<string> findip = new List<string>();
            while (true)
            {
                if (Finder.freePoints.Count < max)
                {
                    if (Finder.RemoteHeight == 0)
                    {
                        if (Finder.testips.TryPeek(out string ttp))
                        {
                            var h = await this.GetHeight(ttp);
                            if (h.height > 0)
                            {
                                Finder.UpdateHeight(h.height);


                            }
                        }

                    }


                    if (Finder.testips.TryDequeue(out string teseip))
                    {
                        findip.Clear();
                        var ping = await this.GetNode(teseip, findip);
                        if (ping > 0)
                        {
                            Finder.freePoints.Enqueue(new Finder.Point() { ip = teseip, ping = ping, state = Finder.PointState.Connect });

                            foreach (var ip in findip)
                            {
                                if (Finder.allips.ContainsKey(ip) == false)
                                {
                                    Finder.testips.Enqueue(ip);
                                    Finder.allips[ip] = 100;
                                }
                            }
                        }
                    }

                }
                else
                {
                    await Task.Delay(100);
                }
            }

        }
    }
    public class Liner
    {
        mywc wc = new mywc();
        int seed;
        int initseed;
        public Liner(int seed)
        {
            this.seed = initseed = seed;
        }

        //会被Ban,所以一定要发现网络
        public async Task FindAddrInTx(string txid, List<string> resultaddr)
        {
            //if (initseed == 0)
            {
                while (true)
                {
                    var b = Finder.freePoints.TryDequeue(out var ip);
                    if (!b)
                    {
                        //退出点1 无影响
                        await Task.Delay(1000);
                        continue;
                    }
                    Interlocked.Increment(ref Finder.usePoints);

                    var useip = "http://" + ip.ip + ":8091/walletsolidity/gettransactionbyid";
                    seed++;
                    if (seed > 65536)
                        seed = 0;
                    //blocknum = 4000000;
                    var senddata = "{\"value\":\"" + txid + "\"}";
                    var data = System.Text.Encoding.ASCII.GetBytes(senddata);
                    JObject json = null;
                    try
                    {
                        var r = await wc.UploadDataTaskAsync(useip, data);
                        if (r.Length < 10)//太短肯定没数据，走人
                        {
                            //退出点2，放回
                            Finder.freePoints.Enqueue(ip);//放回去
                            break;
                        }
                        var got = System.Text.Encoding.ASCII.GetString(r);
                        if (got.IndexOf("this API is closed") == 0)
                        {
                            ip.error = 3;
                            await Task.Delay(10);
                            //退出点三，抛弃此ip
                            continue;
                            //不还了
                        }
                        json = JObject.Parse(got);
                        Finder.freePoints.Enqueue(ip);
                    }
                    catch (Exception err)
                    {
                        ip.error++;
                        if (ip.error > 3)
                        {
                            Finder.testips.Enqueue(ip.ip);
                            //网络错误将来还可以测试，要不然发现节点会用完
                            //退出点4，将来可以测试
                        }
                        else
                        {
                            //退出点5，网络测试 放回
                            Finder.freePoints.Enqueue(ip);//放回去
                        }
                        //Console.WriteLine(" net error=" + err.Message);
                        continue;
                    }
                    finally
                    {
                        Interlocked.Decrement(ref Finder.usePoints);

                    }

                    try
                    {
                        if (json.ContainsKey("raw_data"))
                        {
                            var raw = json["raw_data"] as JObject;
                            if (raw != null && raw.ContainsKey("contract"))
                            {
                                var conlist = raw["contract"] as JArray;
                                foreach (JObject con in conlist)
                                {
                                    var t = con["type"].ToString();
                                    var p = con["parameter"]["value"] as JObject;
                                    if (p.ContainsKey("owner_address"))
                                        resultaddr.Add(p["owner_address"].ToString());
                                    if (p.ContainsKey("to_address"))
                                        resultaddr.Add(p["to_address"].ToString());
                                    if (p.ContainsKey("account_address"))
                                        resultaddr.Add(p["account_address"].ToString());

                                    if (t == "TransferAssetContract" || t == "TransferContract" || t == "AccountUpdateContract" || t == "WithdrawBalanceContract"
                                        ||t== "ParticipateAssetIssueContract"||t== "AssetIssueContract"||t== "WitnessCreateContract"||t== "WitnessUpdateContract")
                                    {

                                    }
                                    else if (t == "UnfreezeBalanceContract" || t == "FreezeBalanceContract")
                                    {

                                    }
                                    else if (t == "VoteWitnessContract")
                                    {
                                        var v = p["votes"] as JArray;
                                        foreach (JObject vo in v)
                                        {
                                            resultaddr.Add(vo["vote_address"].ToString());
                                        }
                                    }
                                    else if (t == "AccountCreateContract")
                                    {
                                        resultaddr.Add(p["account_address"].ToString());
                                    }
                                    else
                                    {
                                        var txt = con.ToString();

                                    }
                                    
                
                                }
                            }

                        }
                    }
                    catch (Exception err)
                    {
                        //Console.WriteLine(" data error=" + err.Message);
                        break;
                    }
                    break;
                }


            }
        }
    }

}
